package cn.sh.library.gmwx.rateLimit;

import cn.sh.library.gmwx.utils.IPUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 限流工具类
 */
@Component
public class RateLimiterUtils {

    @Value("${customer.rate-limit-prefix}")
    private String serverName;

    @Value("${customer.rate-limit-disabled-time:300}")
    private Integer disabledTime;

    @Value("${customer.preview-unit-time:86400}")
    private Integer unitTime;

    @Value("${customer.preview-max-number:1000}")
    private Integer maxNumber;

    @Autowired
    private RedisTemplate<Object, Object> redisTemplate;

    @Autowired
    private RedisScript<Long> limitScript;

    public boolean checkLimitingIp(String ip){
        Object ipObj = redisTemplate.opsForValue().get(serverName + "_limiting_ip");
        if(ipObj != null){
            String[] split = ipObj.toString().split(";");
            for (String lip : split){
                if (lip.equals(ip)){
                    return true;
                }
            }
        }else{
            redisTemplate.opsForValue().set(serverName + "_limiting_ip","1.1.1.1");
        }
        return false;
    }

    public boolean checkLimitingUserId(String uid){
        Object ipObj = redisTemplate.opsForValue().get(serverName + "_limiting_uid");
        if(ipObj != null){
            String[] split = ipObj.toString().split(";");
            for (String lid : split){
                if (lid.equals(uid)){
                    return true;
                }
            }
        }else{
            redisTemplate.opsForValue().set(serverName + "_limiting_uid","0");
        }
        return false;
    }

    /**
     * 限流
     * @param request
     * @param limitKey 标识
     * @param time 单位时间
     * @param count 单位时间内限制访问次数
     * @param dayLimit 是否开启24小时限制访问量
     * @return
     */
    public String limit(HttpServletRequest request, String limitKey, int time, int count, boolean dayLimit){
        try {
            String ipAddr = IPUtils.getIpAddr(request);
            if(checkLimitingIp(ipAddr)){
                return "请求失败！";
            }
            ValueOperations<Object, Object> opsVal = redisTemplate.opsForValue();

            String combineKey = getCombineKey(limitKey, ipAddr);
            String uniKey = "un_" + combineKey;
            if (redisTemplate.hasKey(uniKey)) {
                // 如果key存在，则用户操作频繁，禁用时间未过，不再放行。
                return "操作频繁，请稍候再试！";
            }

            List<Object> keys = Collections.singletonList(combineKey);
            // redis+lua脚本限流
            Long number = redisTemplate.execute(limitScript, keys, count, time);
            if (number==null || number.intValue() > count) {
                // 用户操作频繁，禁止访问一段时间。
                opsVal.set(uniKey, 1, disabledTime, TimeUnit.SECONDS);
                return "操作频繁，请稍候再试！";
            }
            if(dayLimit){
                // 检查24小时内访问次数是否超过最大值。
                List<Object> keys1  = Collections.singletonList("DAY" + combineKey);
                Long number1 = redisTemplate.execute(limitScript, keys1, maxNumber, unitTime);
                if (number1==null || number1.intValue() > maxNumber) {
                    return "您24小时内访问次数已达到上限！";
                }
            }
            return "";
        }catch (Exception e) {
            e.printStackTrace();
            return "系统异常，请稍候再试！";
        }
    }
    private String getCombineKey(String limitKey,String ipAddr) {
        StringBuffer stringBuffer = new StringBuffer(serverName)
                .append(ipAddr)
                .append(limitKey);
        return stringBuffer.toString();
    }

    public List<String[]> getLimitedKeys() {
        List<String[]> result = new ArrayList<>();
        String script = "local keys = redis.call('keys', '\"DAYPDF10.*')\n" +
                "local result = {}\n" +
                "for i, key in ipairs(keys) do\n" +
                "    local value = tonumber(redis.call('get', key))\n" +
                "    if value and value >= 1000 then\n" +
                "        table.insert(result, key)\n" +
                "    end\n" +
                "end\n" +
                "return result";
        RedisScript<List> redisScript = new DefaultRedisScript<>(script, List.class);
        // 执行脚本
        List<String> keys = redisTemplate.execute(redisScript, Collections.emptyList());

        for(String key : keys){
            String[] strings = extractIpAndLetters(key);
            if(strings != null){
                result.add(strings);
            }
        }
        return result;
    }

    public static String[] extractIpAndLetters(String input) {
        // 正则表达式匹配 IP 和下划线前的字母
        Pattern pattern = Pattern.compile("DAYPDF(\\d+\\.\\d+\\.\\d+\\.\\d+)([a-zA-Z]+)_");
        Matcher matcher = pattern.matcher(input);

        if (matcher.find()) {
            String ip = matcher.group(1);
            String letters = matcher.group(2);
            return new String[]{ip, letters};
        }
        return null;
    }

    /**
     * 重置限流
     * @param keyList
     * @param ipAddr
     * @return
     */
    public boolean clear(List<String> keyList,String ipAddr){
        try {
            for (String limitKey:keyList) {
                String combineKey = getCombineKey(limitKey, ipAddr);
                redisTemplate.delete(combineKey);
                redisTemplate.delete("un_" + combineKey);
                redisTemplate.delete("DAY" + combineKey);
            }
            return true;
        }catch (Exception e) {
            return false;
        }
    }
}